﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using System.Reflection;

namespace PSE.Framework.Service.Validation
{
    /// <summary>
    /// Classe responsável por efetuar comparação entre duas propriedades, servindo como
    /// validator na estrutura de validação
    /// </summary>
    internal class PropertyComparisonValidator : Validator, IParentObjectRequiredToValidate
    {
        /// <summary>
        /// Constante representando mensagem para propriedade não encontrada
        /// </summary>
        private const string PROPERTY_NOT_FOUND = "Property not found.";

        /// <summary>
        /// Constante para representar que a propriedade não implementar IComparable 
        /// (e por isso não pode ser comparada no esquema deste validador)
        /// </summary>
        private const string PROPERTY_NOT_ICOMPARABLE = "Property does not implement IComparable";

        /// <summary>
        /// Constante para indicar que os tipos das propriedades comparadas não são compatíveis
        /// para efeitos de comparação
        /// </summary>
        private const string INVALID_PROPERTY_TYPE = "Properties types are not compatible.";

        /// <summary>
        /// Constante para mensagem no caso de exceção gerada porque o _parentObject não foi setado
        /// </summary>
        private const string PARENT_OBJECT_NOT_SET = "Parent object not set.";

        /// <summary>
        /// Nome da propriedade com a qual a comparação deverá ser feita
        /// </summary>
        private string _propertyName;

        /// <summary>
        /// Tipo de comparação a ser realizada
        /// </summary>
        private ComparisonType _comparisonType;

        /// <summary>
        /// Objeto owner da propriedade a ser validada
        /// </summary>
        private object _parentObject = null;

        #region IParentObjectRequiredToValidate Members

        /// <summary>
        /// Implementação necessária para utilizar objeto owner na validação.
        /// Maiores detalhes ver <see cref="IParentObjectRequiredToValidate"/>
        /// </summary>
        public object parentObject
        {
            set { _parentObject = value; }
        }

        #endregion

        /// <summary>
        /// Construtor
        /// </summary>
        /// <param name="propertyName">Nome da propriedade com a qual a comparação deve ser feita</param>
        /// <param name="comparisonType">Tipo de comparação a ser realizada</param>
        public PropertyComparisonValidator(string propertyName, ComparisonType comparisonType)
            : base(null, null)
        {
            _propertyName = propertyName;
            _comparisonType = comparisonType;
        }

        /// <summary>
        /// Método para verificar se determinado tipo (Type) implementa a interface IComparable
        /// </summary>
        /// <param name="tipo">Tipo a ser verificado</param>
        /// <returns>Boolean indicando se o tipo implementa ou não IComparable</returns>
        private bool ImplementIComparable(Type tipo)
        {
            bool implement = false;
            foreach (Type t in tipo.GetInterfaces())
            {
                if (t == typeof(IComparable))
                {
                    implement = true;
                    break;
                }
            }
            return implement;
        }

        /// <summary>
        /// Método que usa Reflection para obter o valor de uma determinada propriedade, cujo 
        /// nome é informado como parâmetro.
        /// Este método irá gerar exceção nos seguintes casos:
        ///  - Propriedade não existe no objeto informado
        ///  - Tipo da propriedade não implementa IComparable
        /// </summary>
        /// <param name="objectToQuery">Objeto de onde o valor deve ser obtido. Seu tipo deve implementar IComparable</param>
        /// <param name="propName">Nome da propriedade de onde o valor deve ser obtido</param>
        /// <returns>Valor obtido (pode ser nulo, se o valor da propriedade for nulo</returns>
        private IComparable GetFieldValue(object objectToQuery, string propName)
        {
            Type tipo = objectToQuery.GetType();
            PropertyInfo propInfo = tipo.GetProperty(propName);
            object returnValue = null;

            if (propInfo != null) // se for null é porque não encontrou a property
            {
                returnValue = propInfo.GetValue(objectToQuery, null);

                if (returnValue != null) // verifica se existe valor
                {
                    if (ImplementIComparable(returnValue.GetType())) // verifica se implementa IComparable
                    {
                        return (IComparable)returnValue;
                    }
                    else // Property not IComparable
                    {
                        throw new Exception(PROPERTY_NOT_ICOMPARABLE);
                    }
                }
                else // Property Value is null
                {
                    return null;
                }
            }
            else // Property not found
            {
                throw new Exception(PROPERTY_NOT_FOUND);
            }
        }

        /// <summary>
        /// Método de implementação requerida (abstract na classe base) que contém a regra de validação 
        /// </summary>
        /// <param name="objectToValidate"></param>
        /// <param name="currentTarget"></param>
        /// <param name="key"></param>
        /// <param name="validationResults"></param>
        public override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)
        {
            if (_parentObject == null)
                throw new Exception(PARENT_OBJECT_NOT_SET);

            IComparable propertyValue = null;
            //Por causa do encapsulamento do atributo de validação (ao invés de herança),
            // currentTarget é igual a objectToValidate, e o owner de objectToValidate na verdade
            // está em _parentObject, setado previamente. Ver detalhes em IParentObjectRequiredToValidate
            propertyValue = GetFieldValue(_parentObject, _propertyName);
            ValueComparisonValidator compareValidator = new ValueComparisonValidator(propertyValue, _comparisonType);
            compareValidator.MessageTemplate = this.MessageTemplate;
            compareValidator.Validate(objectToValidate, validationResults);
            compareValidator = null;
        }

        /// <summary>
        /// Método de implementação requerida (abstract na classe base) e que retorna DefaultMessageTemplate
        /// </summary>
        protected override string DefaultMessageTemplate
        {
            get { return ""; }
        }
    }
}
